This guide is written only for external projects and developers who are not owner and not authorized on the Hub. It is aimed at teams that will normally integrate through subscription or paid requests, and only in rare cases be whitelisted for trials or special agreements.
onlyAggregator.
That means your wallet, frontend, EOA, or contract cannot call adapter pricing directly.
| Component | Address | Why it matters |
|---|---|---|
| CronosOracleHub |
0xcCf26F02C708bc36Cb295783EE85a296a733FfC9
|
Main integration contract. This is the contract your project calls. |
| CronosOracleHubV2PairAdapter |
0x8F99459f41000Db54CAb2394dcbB22AEF285B23b
|
Reference only. External projects do not call this directly. |
| CronosOracleHubV3PairAdapter |
0x32D217bA5f75d20eBdd4dE6Aaa3dA9eC530eBdAF
|
Reference only. External projects do not call this directly. |
| Payment token - USDC |
0xc21223249CA28397B4B6541dfFaEcC539BfF0c59
|
The token used for request fees and subscription payments. |
| WCRO |
0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23
|
Used internally when pricing native CRO as address(0). |
getUsdPrices(address[] tokens) for the paid transaction path.getPricesInToken(address[] tokens, address quoteToken) for the paid transaction path.getUsdPricesView(address[] tokens) only if the caller has free access or access by subscription.getPricesInTokenView(address[] tokens, address quoteToken) only if the caller has free access or access by subscription.buySubscription(uint256 planIndex) to subscribe the caller itself.subscribeFor(address target, uint256 planIndex) to subscribe another address, including a contract address.| Your situation | Recommended function | Why |
|---|---|---|
| Your wallet or frontend wants prices and you are not subscribed | getUsdPrices or getPricesInToken |
These paid functions can pull the request fee from the caller after ERC-20 approval. |
| Your project contract is subscribed or whitelisted | getUsdPricesView or getPricesInTokenView |
These are the cleanest functions for on-chain integration because they avoid per-call token transfers. |
| You want to value assets in USD | getUsdPrices or getUsdPricesView |
Hub returns USD prices scaled to 1e18. |
| You want token-in-token quotes | getPricesInToken or getPricesInTokenView |
Hub derives token-in-quote prices through USD internally. |
Use getUsdPrices or getPricesInToken.
view function.requiredFee = flatRequestFee + (perTokenRequestFee * tokenCount)
Use getUsdPricesView or getPricesInTokenView.
Whitelisting is usually reserved for the Hub owner's own projects or a temporary free trial.
planIndex, not by duration input from the caller.getSubscriptionPlanCount() and getSubscriptionPlanAt(index).subscribeFor(target, planIndex).These values are read directly from the Hub contract when this HTML is opened in a wallet-enabled browser. Replace the placeholder Hub address below before distributing the guide.
getPaymentConfig(), getRequiredRequestFee(tokenCount), getSubscriptionPlanCount(), and getSubscriptionPlanAt(index).
getPaymentConfig().[
{
"inputs": [{"internalType":"address[]","name":"tokens","type":"address[]"}],
"name": "getUsdPrices",
"outputs": [{
"components": [
{"internalType":"uint256","name":"priceE18","type":"uint256"},
{"internalType":"bool","name":"valid","type":"bool"},
{"internalType":"uint8","name":"usedSources","type":"uint8"}
],
"internalType":"struct CronosOracleHub.PriceResult[]",
"name":"results",
"type":"tuple[]"
}],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs": [
{"internalType":"address[]","name":"tokens","type":"address[]"},
{"internalType":"address","name":"quoteToken","type":"address"}
],
"name": "getPricesInToken",
"outputs": [{
"components": [
{"internalType":"uint256","name":"priceE18","type":"uint256"},
{"internalType":"bool","name":"valid","type":"bool"},
{"internalType":"uint8","name":"usedSources","type":"uint8"}
],
"internalType":"struct CronosOracleHub.PriceResult[]",
"name":"results",
"type":"tuple[]"
}],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs": [{"internalType":"address[]","name":"tokens","type":"address[]"}],
"name": "getUsdPricesView",
"outputs": [{
"components": [
{"internalType":"uint256","name":"priceE18","type":"uint256"},
{"internalType":"bool","name":"valid","type":"bool"},
{"internalType":"uint8","name":"usedSources","type":"uint8"}
],
"internalType":"struct CronosOracleHub.PriceResult[]",
"name":"results",
"type":"tuple[]"
}],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [
{"internalType":"address[]","name":"tokens","type":"address[]"},
{"internalType":"address","name":"quoteToken","type":"address"}
],
"name": "getPricesInTokenView",
"outputs": [{
"components": [
{"internalType":"uint256","name":"priceE18","type":"uint256"},
{"internalType":"bool","name":"valid","type":"bool"},
{"internalType":"uint8","name":"usedSources","type":"uint8"}
],
"internalType":"struct CronosOracleHub.PriceResult[]",
"name":"results",
"type":"tuple[]"
}],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [{"internalType":"uint256","name":"planIndex","type":"uint256"}],
"name": "buySubscription",
"outputs": [],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs": [
{"internalType":"address","name":"target","type":"address"},
{"internalType":"uint256","name":"planIndex","type":"uint256"}
],
"name": "subscribeFor",
"outputs": [],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs": [],
"name": "getSubscriptionPlanCount",
"outputs": [{"internalType":"uint256","name":"count","type":"uint256"}],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [{"internalType":"uint256","name":"index","type":"uint256"}],
"name": "getSubscriptionPlanAt",
"outputs": [
{"internalType":"uint64","name":"durationSeconds","type":"uint64"},
{"internalType":"uint256","name":"price","type":"uint256"}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [{"internalType":"address","name":"subscriber","type":"address"}],
"name": "getSubscriptionDetails",
"outputs": [
{"internalType":"uint64","name":"expiry","type":"uint64"},
{"internalType":"bool","name":"active","type":"bool"},
{"internalType":"uint256","name":"remainingSeconds","type":"uint256"}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [{"internalType":"address","name":"user","type":"address"}],
"name": "getAccessDetails",
"outputs": [
{"internalType":"bool","name":"whitelisted","type":"bool"},
{"internalType":"bool","name":"subscribed","type":"bool"},
{"internalType":"bool","name":"hasFreeAccess","type":"bool"}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [],
"name": "getPaymentConfig",
"outputs": [
{"internalType":"address","name":"paymentToken_","type":"address"},
{"internalType":"uint256","name":"flatRequestFee_","type":"uint256"},
{"internalType":"uint256","name":"perTokenRequestFee_","type":"uint256"}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs": [{"internalType":"uint256","name":"tokenCount","type":"uint256"}],
"name": "getRequiredRequestFee",
"outputs": [{"internalType":"uint256","name":"requiredFee","type":"uint256"}],
"stateMutability":"view",
"type":"function"
}
]
[
{
"inputs": [
{"internalType":"address","name":"spender","type":"address"},
{"internalType":"uint256","name":"amount","type":"uint256"}
],
"name":"approve",
"outputs":[{"internalType":"bool","name":"","type":"bool"}],
"stateMutability":"nonpayable",
"type":"function"
}
]
[
{
"inputs": [{"internalType":"address[]","name":"tokens","type":"address[]"}],
"name":"getUsdPrices",
"outputs":[{
"components":[
{"internalType":"uint256","name":"priceE18","type":"uint256"},
{"internalType":"uint256","name":"liquidityUsdE18","type":"uint256"},
{"internalType":"bool","name":"valid","type":"bool"}
],
"internalType":"struct IPriceAdapter.AdapterPrice[]",
"name":"prices",
"type":"tuple[]"
}],
"stateMutability":"view",
"type":"function"
}
]
onlyAggregator, so direct external adapter pricing calls will revert unless the caller is the configured Hub, owner, or an authorized address on the adapter.
import { ethers } from "ethers";
const HUB = "[CronosOracleHub address]";
const hub = new ethers.Contract(HUB, HUB_ABI, signerOrProvider);
const count = Number(await hub.getSubscriptionPlanCount());
for (let i = 0; i < count; i++) {
const [durationSeconds, price] = await hub.getSubscriptionPlanAt(i);
console.log({
planIndex: i,
durationSeconds: Number(durationSeconds),
price: price.toString()
});
}
const HUB = "[CronosOracleHub address]";
const PAYMENT_TOKEN = "[Payment token address]";
const PLAN_INDEX = 0;
const hub = new ethers.Contract(HUB, HUB_ABI, signer);
const token = new ethers.Contract(PAYMENT_TOKEN, ERC20_ABI, signer);
const [, price] = await hub.getSubscriptionPlanAt(PLAN_INDEX);
const approveTx = await token.approve(HUB, price);
await approveTx.wait();
const subscribeTx = await hub.buySubscription(PLAN_INDEX);
await subscribeTx.wait();
console.log("Subscription bought for connected wallet");
const HUB = "[CronosOracleHub address]";
const PAYMENT_TOKEN = "[Payment token address]";
const PROJECT_CONTRACT = "[Your contract address]";
const PLAN_INDEX = 1;
const hub = new ethers.Contract(HUB, HUB_ABI, signer);
const token = new ethers.Contract(PAYMENT_TOKEN, ERC20_ABI, signer);
const [, price] = await hub.getSubscriptionPlanAt(PLAN_INDEX);
await (await token.approve(HUB, price)).wait();
await (await hub.subscribeFor(PROJECT_CONTRACT, PLAN_INDEX)).wait();
const details = await hub.getSubscriptionDetails(PROJECT_CONTRACT);
console.log({
expiry: details.expiry?.toString?.() ?? details[0].toString(),
active: details.active ?? details[1],
remainingSeconds: (details.remainingSeconds ?? details[2]).toString()
});
const HUB = "[CronosOracleHub address]";
const PAYMENT_TOKEN = "[Payment token address]";
const TOKENS = [
"0x0000000000000000000000000000000000000000", // native CRO
"0xTokenA",
"0xTokenB"
];
const hub = new ethers.Contract(HUB, HUB_ABI, signer);
const token = new ethers.Contract(PAYMENT_TOKEN, ERC20_ABI, signer);
const requiredFee = await hub.getRequiredRequestFee(TOKENS.length);
await (await token.approve(HUB, requiredFee)).wait();
const tx = await hub.getUsdPrices(TOKENS);
const receipt = await tx.wait();
console.log("Paid price request executed", receipt.hash);
// To read returned values off-chain without sending a paid tx, simulate with callStatic:
const results = await hub.callStatic.getUsdPrices(TOKENS);
console.log(results.map(r => ({
priceE18: r.priceE18.toString(),
valid: r.valid,
usedSources: Number(r.usedSources)
})));
callStatic for UI display.
const HUB = "[CronosOracleHub address]";
const hub = new ethers.Contract(HUB, HUB_ABI, providerOrSigner);
const access = await hub.getAccessDetails("[Caller address]");
if (!(access.hasFreeAccess ?? access[2])) {
throw new Error("Caller does not have free access");
}
const results = await hub.getUsdPricesView([
"0x0000000000000000000000000000000000000000",
"0xTokenA"
]);
console.log(results.map(r => ({
priceE18: r.priceE18.toString(),
valid: r.valid,
usedSources: Number(r.usedSources)
})));
const HUB = "[CronosOracleHub address]";
const QUOTE_TOKEN = "0xUSDCorOtherQuoteToken";
const results = await hub.getPricesInTokenView(
["0xTokenA", "0xTokenB"],
QUOTE_TOKEN
);
for (const r of results) {
console.log({
priceInQuoteE18: r.priceE18.toString(),
valid: r.valid,
usedSources: Number(r.usedSources)
});
}
interface ICronosOracleHub {
struct PriceResult {
uint256 priceE18;
bool valid;
uint8 usedSources;
}
function getUsdPricesView(address[] calldata tokens)
external
view
returns (PriceResult[] memory results);
function getPricesInTokenView(address[] calldata tokens, address quoteToken)
external
view
returns (PriceResult[] memory results);
function getUsdPrices(address[] calldata tokens)
external
returns (PriceResult[] memory results);
function getPricesInToken(address[] calldata tokens, address quoteToken)
external
returns (PriceResult[] memory results);
function getAccessDetails(address user)
external
view
returns (bool whitelisted, bool subscribed, bool hasFreeAccess);
}
contract MyProtocol {
ICronosOracleHub public immutable hub;
constructor(address hub_) {
hub = ICronosOracleHub(hub_);
}
function getTokenUsd(address token) external view returns (uint256 priceE18, bool valid) {
address[] memory tokens = new address[](1);
tokens[0] = token;
ICronosOracleHub.PriceResult[] memory results = hub.getUsdPricesView(tokens);
return (results[0].priceE18, results[0].valid);
}
}
interface IERC20ApproveOnly {
function approve(address spender, uint256 amount) external returns (bool);
}
contract MyPaidProtocol {
ICronosOracleHub public immutable hub;
IERC20ApproveOnly public immutable paymentToken;
constructor(address hub_, address paymentToken_) {
hub = ICronosOracleHub(hub_);
paymentToken = IERC20ApproveOnly(paymentToken_);
}
function paidRead(address token, uint256 feeAmount)
external
returns (uint256 priceE18, bool valid)
{
paymentToken.approve(address(hub), feeAmount);
address[] memory tokens = new address[](1);
tokens[0] = token;
ICronosOracleHub.PriceResult[] memory results = hub.getUsdPrices(tokens);
return (results[0].priceE18, results[0].valid);
}
}
| Field | Type | Meaning |
|---|---|---|
priceE18 |
uint256 |
Price scaled to 1e18. Example: 1.25 USD = 1250000000000000000. |
valid |
bool |
true if the Hub could produce a valid aggregated price for that token. |
usedSources |
uint8 |
How many adapter quotes survived filtering and were actually used in the final result. |
valid before trusting priceE18. A zero or default-looking number should not be trusted unless valid == true.
address(0). The Hub internally normalizes that to WCRO.1000000 means 1.0.External projects should integrate with CronosOracleHub, not directly with the adapter. The adapter pricing entrypoint is restricted and is intended to be called by the Hub.
The normal long-term model is subscription. External users can also use the paid request path, but subscriptions are usually cleaner for repeated usage and for protocol integrations.
Use the paid request path when the caller does not have free access and you want to fetch prices anyway. In that case the caller needs sufficient payment tokens and must approve the Hub first.
Use the free view path when the caller has free access, for example because it is actively subscribed or whitelisted. This is usually the preferred pattern for project contracts.
You need enough ERC-20 allowance for the Hub to collect the required fee. Whether you approve each time or approve a larger amount is your own operational choice and risk decision.
Yes. A payer can subscribe a target address using subscribeFor(target, planIndex). That target can be a smart contract address, which is the recommended setup for protocol integrations.
The new plan duration is added after the current subscription expiry. It does not overwrite the active remaining period.
Native CRO is represented as address(0). The Hub internally normalizes that to WCRO where required.
priceE18 is the returned price scaled to 1e18. For example, a USD price of 1.25 would be returned as 1250000000000000000.
No. Always check the valid field first. You should not rely on a returned number unless the result is marked valid.
It tells you how many adapter quotes survived filtering and were actually used in the final aggregated result.
No. External projects should treat whitelisting as temporary unless explicitly agreed otherwise. For long-term integrations, build around subscription access.
No. Paid request fees and blockchain gas costs are separate. The request fee is collected in the configured payment token, while gas is paid to the network.
Yes. Use getPricesInToken or getPricesInTokenView with the quote token address when you want token-in-token pricing instead of USD pricing.
Yes, token support can be requested. Contact Swerfer via X or Telegram using the links in the footer and send at least the following:
Providing both addresses helps evaluate whether the token can be added cleanly to the pricing configuration.